﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;

namespace XtremeMvvm
{
    public static class MvvmHelper
    {
        private static ManualResetEvent dialogResetEvent;
        public static bool IsShowingModalDialog
        {
            get { return !dialogResetEvent.WaitOne(0); }
        }

        private static Window dialog;
        public static Window ModalDialogWindow
        {
            get
            {
                return IsShowingModalDialog ? dialog : null;
            }
            private set
            {
                dialog = value;
            }
        }

        static MvvmHelper()
        {
            dialogResetEvent= new ManualResetEvent(true);
            dialog = null;
        }

        public static bool IsInDesigner
        {
            get
            {
                DependencyProperty property = DesignerProperties.IsInDesignModeProperty;
                bool isInDesigner = (bool)DependencyPropertyDescriptor
                    .FromProperty(property, typeof(FrameworkElement))
                    .Metadata.DefaultValue;
                return isInDesigner;
            }
        }

        public static T GetProperty<T>(DependencyProperty property)
        {
            DependencyObject dependencyObject = GetViewModel.ConcreteImplementation as DependencyObject;
            if (dependencyObject == null)
            {
                throw new InvalidOperationException();
            }

            Monitor.Enter(property);
            try
            {
                return (T)dependencyObject.Dispatcher.Invoke(new Func<DependencyProperty, T>
                (
                    delegate(DependencyProperty dp)
                    {
                        return (T)dependencyObject.GetValue(dp);
                    }
                ), property);
            }
            catch (TargetInvocationException e)
            {
                if (e.InnerException is InvalidCastException)
                {
                    return default(T);
                }
                else
                {
                    throw;
                }
            }
            catch (NullReferenceException)
            {
                // The app has likely shut down, eat the exception and return the default
                return default(T);
            }
            finally
            {
                Monitor.Exit(property);
            }
        }

        public static void SetProperty<T>(DependencyProperty property, T value)
        {
            SetProperty(property, value, DispatcherPriority.Normal);
        }

        public static void SetProperty<T>(DependencyProperty property, T value, DispatcherPriority priority)
        {
            DependencyObject dependencyObject = GetViewModel.ConcreteImplementation as DependencyObject;
            if (dependencyObject == null)
            {
                throw new InvalidOperationException();
            }

            if (property.IsValidType(value))
            {
                Monitor.Enter(property);
                try
                {
                    dependencyObject.Dispatcher.Invoke
                    (
                        new Action(() => { dependencyObject.SetValue(property, value); }), priority
                    );
                }
                finally
                {
                    Monitor.Exit(property);
                }
            }
            else
            {
                throw new ArgumentException(String.Format("Property {0} cannot be set to a value of {1}", property.Name, value.ToString()));
            }
        }

        public static void ResetProperty(DependencyProperty property)
        {
            MvvmHelper.SetProperty(property, property.DefaultMetadata.DefaultValue);
        }

        internal static void NotifyPropertyChanged<T>(Expression<Func<ViewModelBase, T>> property, PropertyChangedEventHandler eventHandler)
        {
            NotifyPropertyChanged(property, eventHandler, DispatcherPriority.Normal);
        }

        internal static void NotifyPropertyChanged<T>(Expression<Func<ViewModelBase, T>> property, PropertyChangedEventHandler eventHandler, DispatcherPriority priority)
        {
            MemberExpression memberExpression = property.Body as MemberExpression;
            if ((memberExpression != null) && (eventHandler != null))
            {
                Monitor.Enter(memberExpression.Member);
                try
                {
                    Application.Current.Dispatcher.Invoke
                    (
                        new Action(() => { eventHandler(GetViewModel.ConcreteImplementation, new PropertyChangedEventArgs(memberExpression.Member.Name)); }), priority
                    );
                }
                finally
                {
                    Monitor.Exit(memberExpression.Member);
                }
            }
        }

        public static IEnumerable<T> EnumerateChildContols<T>(DependencyObject dependencyObject) where T : DependencyObject
        {
            if (dependencyObject != null)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObject); i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(dependencyObject, i);
                    if (child != null && child is T)
                    {
                        yield return (T)child;
                    }
                    foreach (T childOfChild in EnumerateChildContols<T>(child))
                    {
                        yield return childOfChild;
                    }
                }
            }
        }

        public static void ShowDialog(string looseXamlLocation, Application app)
        {
            ShowDialog(looseXamlLocation, app, null);
        }

        public static void ShowDialog(string looseXamlLocation, Application app, DependencyProperty returnValue)
        {
            ShowDialogInternal(looseXamlLocation, app, returnValue);
        }

        public static void ShowDialog(string looseXamlLocation)
        {
            ShowDialog(looseXamlLocation, (Window)null, null);
        }

        public static void ShowDialog(string looseXamlLocation, Window window)
        {
            ShowDialog(looseXamlLocation, window, null);
        }

        public static void ShowDialog(string looseXamlLocation, DependencyProperty returnValue)
        {
            ShowDialog(looseXamlLocation, (Window)null, returnValue);
        }

        public static void ShowDialog(string looseXamlLocation, Window window, DependencyProperty returnValue)
        {
            ShowDialogInternal(looseXamlLocation, window, returnValue);
        }
            
        private static void ShowDialogInternal<T>(string looseXamlLocation, T dispatchObject, DependencyProperty returnValue) where T : DispatcherObject
        {
            bool? dialogResult = null;

            if (!IsShowingModalDialog)
            {
                dialogResetEvent.Reset();
                Thread worker = new Thread(() =>
                {
                    try
                    {
                        if (dispatchObject == null)
                        {
                            ModalDialogWindow = LooseXaml.Load(looseXamlLocation) as Window;
                            if (ModalDialogWindow != null)
                            {
                                dialogResult = ModalDialogWindow.ShowDialog();
                            }
                        }
                        else
                        {
                            dispatchObject.Dispatcher.Invoke(new Action<T>(parent =>
                            {
                                ModalDialogWindow = LooseXaml.Load(looseXamlLocation) as Window;
                                if (ModalDialogWindow != null)
                                {
                                    if (parent is Window)
                                    {
                                        ModalDialogWindow.Owner = parent as Window;
                                    }
                                    else if (parent is Application)
                                    {
                                        Application app = parent as Application;
                                        ModalDialogWindow.Owner = app.MainWindow;
                                    }
                                    dialogResult = ModalDialogWindow.ShowDialog();
                                }
                            }), dispatchObject);
                        }

                        if (returnValue != null)
                        {
                            MvvmHelper.SetProperty(returnValue, dialogResult);
                        }
                    }
                    finally
                    {
                        dialogResetEvent.Set();
                    }
                });
                worker.Name = "XtremeMvvm Modal Dialog";
                worker.Start();
                dialogResetEvent.WaitOne();
            }
        }

        public static void CloseDialog()
        {
            if (IsShowingModalDialog)
            {
                Window dialog = ModalDialogWindow;
                if (dialog != null)
                {
                    dialog.Dispatcher.Invoke(new Action(dialog.Close));
                }
            }
        }

        public static void RequerySuggested()
        {
            Application.Current.Dispatcher.BeginInvoke(new Action(() => { CommandManager.InvalidateRequerySuggested(); }));
        }
    }
}
